#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "ctnode.h"
#include "parser.h"
#include "wavtrack.h"
#include "memorystream.h"
#include "filestream.h"
#include "wavstream.h"

int yyparse (ParserState *state);
void yyerror (ParserState *state, char *s);

extern FILE *yyin;

void print_ctnode (CTNode *node, void *data)
{
  printf ("[%p] Type: %d Value: %d Partner: %p\n", node, node->type,
          node->value, node->partner);
}

void ctnode_free_wrap (CTNode *node, void *data)
{
  CTList *list = (CTList *)data;
  ctlist_remove_node (list, node);
  free (node);
}

void ctnode_process_node_wrap (CTNode *node, void *data)
{
  Track *track = (Track *)data;
  track->process_node (track, node);
}

void print_usage (char *prog_name, char *message, char *arg)
{
  if (arg)
    printf ("%s %s\n\n", message, arg);
  else if (message)
    printf ("%s\n\n", message);

  printf ("Usage: %s [options]\n\n", prog_name);
  printf ("Options:\n");
  printf ("  -i [file]    Specify input file\n");
  printf ("  -o [file]    Specify output file\n");
  printf ("  -b [beat]    Use [beat].wav for the beat\n");
}

int main (int argc, char **argv)
{
  int i;
  char *input = NULL;
  char *output = NULL;
  char *beat = "beat";
  char beat_file[256];
  FILE *beatf;
  RiffChunk rchunk;
  char *beat_buff;
  MemoryStream ms;
  ParserState pstate;
  WavTrack wav_track;
  CTNode *cur;
  int repeat_cnt;
  FileStream fs;
  char tmpb[1024];
  int nread;

  for (i = 1; i < argc; ++i) {
    if (strcmp (argv[i], "-i") == 0) {
      if (i + 1 >= argc) {
        print_usage (argv[0], "No input file given", NULL);
        return 1;
      }

      input = argv[i + 1];
      ++i;
    }
    else if (strcmp (argv[i], "-o") == 0) {
      if (i + 1 >= argc) {
        print_usage (argv[0], "No output file given", NULL);
        return 1;
      }

      output = argv[i + 1];
      ++i;
    }
    else if (strcmp (argv[i], "-b") == 0) {
      if (i + 1 >= argc) {
        print_usage (argv[0], "No beat given", NULL);
        return 1;
      }

      beat = argv[i + 1];
      ++i;
    }
    else {
      print_usage (argv[0], "Invalid option", argv[i]);
      return 1;
    }
  }

  if (!input) {
    print_usage (argv[0], "Input option required", NULL);
    return 1;
  }

  if (!output) {
    print_usage (argv[0], "Output option required", NULL);
    return 1;
  }

  memset (beat_file, 0, 256);

  sprintf (beat_file, "beats/%s.wav", beat);
  beatf = fopen (beat_file, "rb");
  if (!beatf) {
    printf ("Unable to open beat file %s\n", beat_file);
    return 1;
  }

  fread (&rchunk, sizeof (rchunk), 1, beatf);

  if (strncmp (rchunk.id, "RIFF", 4)
      || strncmp (rchunk.wave_id, "WAVE", 4)) {
    printf ("Invalid format for %s\n", beat_file);
    return 1;
  }

  beat_buff = malloc (8 + rchunk.size);
  assert (beat_buff);

  fseek (beatf, 0, SEEK_SET);
  fread (beat_buff, 1, 8 + rchunk.size, beatf);

  memorystream_create (&ms, 1024);

  yyin = fopen (input, "r");

  parserstate_create (&pstate);
  if (yyparse (&pstate))
    return 1;

  if (pstate.repeat_stack.top >= 0)
    yyerror (&pstate, "missing end repeat separator");

  //ctlist_forall (&pstate.track, print_ctnode, NULL);

  wavtrack_create (&wav_track, (WavChunk *)beat_buff, STREAM_CAST (&ms));

  cur = pstate.track.head;
  while (cur != NULL) {
    switch (cur->type) {
      case RepeatBeginElement:
        repeat_cnt = 0;
        break;

      case RepeatEndElement:
        if (repeat_cnt < cur->value) {
          ++repeat_cnt;
          cur = cur->partner->next;
          continue;
        }

        break;
    }

    wav_track.track.process_node (TRACK_CAST (&wav_track), cur);
    cur = cur->next;
  }

  wav_track.track.finalize (TRACK_CAST (&wav_track));

  filestream_create (&fs, output);
  ms.stream.seek (STREAM_CAST (&ms), 0, SeekSet);
  nread = ms.stream.read (STREAM_CAST (&ms), tmpb, 1024, 0);
  while (nread) {
    fs.stream.write (STREAM_CAST (&fs), tmpb, nread);
    nread = ms.stream.read (STREAM_CAST (&ms), tmpb, 1024, 0);
  }

  fs.stream.close (STREAM_CAST (&fs));
  ms.stream.close (STREAM_CAST (&ms));

  ctlist_forall (&pstate.track, ctnode_free_wrap, &pstate.track);

  free (beat_buff);

  return 0;
}
